/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once 

#include <baja/app/debug_console.hpp>
#include <baja/app/var_console.hpp>
#include <baja/common.hpp>
#include <baja/graphics.hpp>
#include <baja/math.hpp>
#include <boost/format.hpp>

namespace baja { namespace app {

namespace b = baja;
namespace g = baja::graphics;
namespace m = baja::math;

BAJA_BEGIN_CONSTANTS(debug_console_constants)
    BAJA_DECLARE_CONST(b::uint32, consoleFontEmHeight, 18)
    BAJA_DECLARE_CONST(b::float32, consoleFontPixelHeight, 16.f)
    BAJA_DECLARE_CONST(b::float32, spacingBetweenLines, 0.5f)
    BAJA_DECLARE_CONST(b::float32, leftMargin, 8.f)
BAJA_END_CONSTANTS

class debug_console : public idebug_console
{
public:
    
    debug_console(const b::int32 lineCount) : _nextTrace(0), _lineCount(lineCount), _isVisible(false)
    {
        _root = b::createVarGroup(L"debug");
        _traces.resize(lineCount);
    }

    static std::shared_ptr<debug_console> create(const b::int32 lineCount)
    {
        std::shared_ptr<debug_console> returnValue = std::make_shared<debug_console>(lineCount);
        return returnValue;
    }

    void onAddedToRenderService(const std::shared_ptr<g::irender_service>& renderService) {}

    void onRemovedFromRenderService(const std::shared_ptr<g::irender_service>& renderService) {}

    std::shared_ptr<b::ivar_group> getRoot()
    {
        return _root;
    }

    bool getIsVisible()
    {
        return _isVisible;
    }

    void setIsVisible(bool isVisible)
    {
        _isVisible = isVisible;
    }
    
    void trace(const wchar* text)
    {
        BAJA_LOCK(_mutex);

        _traces[_nextTrace % _lineCount] = text;
        _nextTrace++;
    }

    void clear()
    {
        BAJA_LOCK(_mutex);
        _nextTrace = 0;
    }

    // irender_object

    b::int32 getPriority()
    {
        // debug console should always be on top of everything else
        return INT_MAX;
    }

    void render(const std::shared_ptr<g::iengine>& graphics)
    {
        if (_isVisible)
        {
            // load font when needed
            if (_consoleFont.get() == nullptr)
            {
                // get asset manager
                std::shared_ptr<g::iasset_manager> assetManager = graphics->getAssetManager();
                _consoleFont = assetManager->getOrCreateFont(
                    graphics, 
                    L"Consolas",
                    debug_console_constants::consoleFontEmHeight,
                    false
                    );
            }

            // make sure normal render mode is being used
            g::render_mode originalRenderMode = graphics->getRenderMode();
            graphics->setRenderMode(g::render_mode::normal);
            
            graphics->setZBuffer(g::enabled_state::disabled);
            graphics->setCulling(g::cull_state::none);

            float32 row = 0.f;

            g::viewport vp;
            graphics->getViewport(&vp);

            row = this->renderVars(graphics, row, vp);   
            this->renderRow(
                graphics,
                L"-------------------------------------------",
                row,
                0,
                vp
                );
            row++;
            row = this->renderTraces(graphics, row, vp);

            // restore render mode back to original state
            graphics->setRenderMode(originalRenderMode);
        }
    }

private:

    float32 renderTraces(const std::shared_ptr<g::iengine>& graphics, const float32 row, const g::viewport& vp)
    {
        float32 returnValue = row;

        for (
            int count = 0, i = _nextTrace - 1;
            count <= _lineCount && i >= 0;
            count++, i--
            )
        {
            std::wstring str = _traces[i % _lineCount];

            this->renderRow(graphics, str.c_str(), returnValue, 0, vp);
            returnValue++;
        }

        return returnValue;
    }

    float32 renderVars(
        const std::shared_ptr<g::iengine>& graphics, 
        const float32 row,
        const g::viewport& vp
        )
    {
        float returnValue = row;

        // build up the row data, as needed
        if (_rowData.size() == 0)
        {
            this->buildRowData();
        }

        // only continue if we have data to display
        if (_rowData.size() > 0)
        {
            for(uint32 i = 0; i < _rowData.size(); i++)
            {
                std::shared_ptr<ivar_wrapper> var = std::dynamic_pointer_cast<ivar_wrapper>(_rowData[i].lock());
                if (var)
                {
                    std::wstring varName = var->getName();
                    std::wstring varValue = var->getDataAsString();
                    std::wstring textToDisplay = (boost::wformat(L"%s: %s") % varName.c_str() % varValue.c_str()).str();

                    uint32 flags = var->getFlags();
                    this->renderRow(graphics, textToDisplay.c_str(), returnValue, flags, vp);
                }

                returnValue++;
            }
        }

        return returnValue;
    }

    void renderRow(
        const std::shared_ptr<g::iengine>& graphics,
        const wchar* text,
        const float32 row,
        const uint32 flags,
        const g::viewport& vp
        )
    {
        g::packed_color color = g::packColor(0xff, 0xff, 0xff, 0xff);
        g::packed_color shadowColor = g::packColor(0xff, 0x0, 0x0, 0x0);

        float32 top = vp.height - (row + 1) * debug_console_constants::consoleFontPixelHeight;
        m::rectangle rect(
            debug_console_constants::leftMargin, 
            top,
            vp.width, 
            top + debug_console_constants::consoleFontPixelHeight
            );

        // draw drop shadow
        _consoleFont->renderText(
            graphics,
            text,
            shadowColor,
            g::horizontal_alignment::left,
            g::vertical_alignment::top,
            &rect
            );

        rect.top -= 2;
        rect.left -= 2;
        
        // draw text
        _consoleFont->renderText(
            graphics,
            text,
            color,
            g::horizontal_alignment::left,
            g::vertical_alignment::top,
            &rect
            );
    }

    void buildRowData()
    {
        // make sure we clear all current row data
        _rowData.clear();

        // add all vars
        size_t count = this->getRoot()->getVarCount();

        for (size_t i = 0; i < count; i++)
        {
            std::shared_ptr<b::ivar_wrapper> var = this->getRoot()->getVar(i);
            std::weak_ptr<b::ibase> base = std::dynamic_pointer_cast<b::ibase>(var);
            _rowData.push_back(base);
        }
    }

private:
    std::recursive_mutex _mutex;
    std::vector<std::wstring> _traces;
    int32 _nextTrace;
    std::vector<std::weak_ptr<b::ibase>> _rowData; 
    std::shared_ptr<g::ifont> _consoleFont;
    std::shared_ptr<b::ivar_group> _root;
    std::shared_ptr<b::ivar_group> _varGroup; // used for var console settings
    bool _isVisible;
    b::int32 _lineCount;
};

inline std::shared_ptr<idebug_console> createDebugConsole(const b::int32 lineCount)
{
    std::shared_ptr<debug_console> returnValue = debug_console::create(lineCount);
    return std::dynamic_pointer_cast<idebug_console>(returnValue);
}

}} // namespace baja::app
